#pragma once

#include "option.h"

namespace sfc::result {

namespace detail {

template <class T, class E>
struct Inn {
  bool _tag;
  union {
    T _ok;
    E _err;
  };

 public:
  [[sfc_inline]] Inn(T ok) : _tag{true}, _ok{static_cast<T&&>(ok)} {}

  [[sfc_inline]] Inn(E err) : _tag{false}, _err{static_cast<E&&>(err)} {}

  [[sfc_inline]] ~Inn() noexcept = default;
  [[sfc_inline]] ~Inn() requires(!trait::TvDrop<T, E>) {
    _tag ? _ok.~T() : _err.~E();
  }

  [[sfc_inline]] Inn(Inn&&) noexcept = default;
  [[sfc_inline]] Inn(Inn&& other) noexcept requires(!trait::TvCopy<T, E>) : _tag{other._tag} {
    _tag ? (void)new (mem::inplace_t{}, &_ok) T{static_cast<T&&>(other._ok)}
         : (void)new (mem::inplace_t{}, &_err) T{static_cast<T&&>(other._err)};
  }

  [[sfc_inline]] Inn(const Inn&) noexcept = default;
  [[sfc_inline]] Inn(const Inn& other) requires(!trait::TvCopy<T, E>) : _tag{other._tag} {
    _tag ? (void)new (mem::inplace_t{}, &_ok) T{other._ok}
         : (void)new (mem::inplace_t{}, &_err) T{other._err};
  }

  [[sfc_inline]] Inn& operator=(const Inn&) = default;
  [[sfc_inline]] Inn& operator=(const Inn& other) requires(!trait::TvCopy<T, E>) {
    if (_tag == other._tag) {
      _tag ? _ok = other._ok : _err = other._err;
    } else {
      _tag ? _ok.~T() : _err.~E();
      _tag = other._tag;
      _tag ? new (mem::inplace_t{}, &_ok) T{other._ok}
           : new (mem::inplace_t{}, &_err) E{other._err};
    }
    return *this;
  }

  [[sfc_inline]] Inn& operator=(Inn&&) = default;
  [[sfc_inline]] Inn& operator=(Inn&& other) noexcept requires(!trait::TvCopy<T, E>) {
    if (_tag == other._tag) {
      _tag ? _ok = static_cast<T&&>(other._ok) : _err = static_cast<E&&>(other._er);
    } else {
      _tag ? _ok.~T() : _err.~E();
      _tag = other._tag;
      _tag ? new (mem::inplace_t{}, &_ok) T{static_cast<T&&>(other._ok)}
           : new (mem::inplace_t{}, &_err) E{static_cast<E&&>(other._err)};
    }
    return *this;
  }

  [[sfc_inline]] operator bool() const {
    return _tag;
  }

  [[sfc_inline]] auto get_ok_unchecked() const -> const T& {
    return _ok;
  }

  [[sfc_inline]] auto get_ok_unchecked_mut() -> T& {
    return _ok;
  }

  [[sfc_inline]] auto get_err_unchecked() const -> const E& {
    return _err;
  }

  [[sfc_inline]] auto get_err_unchecked_mut() -> E& {
    return _err;
  }
};

}  // namespace detail

template <class T, class E>
class Result {
  detail::Inn<T, E> _inn;

 public:
  [[sfc_inline]] Result(T val) noexcept : _inn{static_cast<T&&>(val)} {}

  [[sfc_inline]] Result(E err) noexcept : _inn{static_cast<E&&>(err)} {}

  [[sfc_inline]] ~Result() = default;

  [[sfc_inline]] Result(const Result&) = default;

  [[sfc_inline]] Result(Result&&) noexcept = default;

  [[sfc_inline]] Result& operator=(Result&&) noexcept = default;

  [[sfc_inline]] Result& operator=(const Result&) = default;

  [[sfc_inline]] auto is_ok() const -> bool {
    return _inn;
  }

  [[sfc_inline]] auto is_err() const -> bool {
    return !_inn;
  }

  [[sfc_inline]] auto get_ok_unchecked() const -> const T& {
    return _inn.get_ok_unchecked();
  }

  [[sfc_inline]] auto get_ok_unchecked_mut() -> T& {
    return _inn.get_ok_unchecked_mut();
  }

  [[sfc_inline]] auto get_err_unchecked() const -> const E& {
    return _inn.get_err_unchecked();
  }

  [[sfc_inline]] auto get_err_unchecked_mut() -> E& {
    return _inn.get_err_unchecked_mut();
  }

  auto ok() && -> option::Option<T> {
    if (!_inn) return {};
    return static_cast<T&&>(_inn.get_ok_unchecked_mut());
  }

  auto ok() const& -> option::Option<T> {
    if (!_inn) return {};
    return _inn.get_ok_unchecked();
  }

  auto err() && -> option::Option<E> {
    if (_inn) return {};
    return static_cast<E&&>(_inn.get_err_unchecked_mut());
  }

  auto err() const& -> option::Option<E> {
    if (_inn) return {};
    return _inn.get_err_unchecked();
  }

  auto unwrap() && -> T {
    panicking::assert_fmt(_inn._tag, "Result::unwrap: Err({})", _inn.get_err_unchecked());
    return static_cast<T&&>(_inn.get_ok_unchecked_mut());
  }

  auto unwrap() const& -> T {
    panicking::assert_fmt(_inn._tag, "Result::unwrap: Err({})", _inn.get_err_unchecked());
    return _inn.get_ok_unchecked();
  }

  auto unwrap_err() && -> E {
    panicking::assert_fmt(!_inn, "Result::unwrap_err: Ok({})", _inn.get_ok_unchecked());
    return static_cast<E&&>(_inn.get_err_unchecked_mut());
  }

  auto unwrap_err() const& -> E {
    panicking::assert_fmt(!_inn, "Result::unwrap_err: Ok({})", _inn.get_ok_unchecked());
    return _inn.get_err_unchecked();
  }
};

}  // namespace sfc::result
